﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using System.IO.Compression;
using System.ComponentModel;
using System.Xml.Serialization;
using Microsoft.Win32;
using FTP.Client;
using FTP.Client.Common;
using Mimoza.Common;

namespace SystemUpdate
{
    public class UpdateSettings : INotifyPropertyChanged
    {
        private const string REGISTRY_SETTINGS_KEY = "SOFTWARE\\SystemUpdate";
        private bool downloadFromHTTP = false;
        private bool downloadFromFTP  = false;
        private bool downloadFromUSB  = false;

        private string sourceServer;
        private string sourceFile;
        private bool sourceAuthenticationIsRequried = false;
        private string sourceUser;
        private string sourcePassword;

        private bool useProxy = false;
        private string proxyAddress;
        private bool proxyAuthenticationIsRequried = false;
        private string proxyUser;
        private string proxyPassword;

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        public bool DownloadFromHTTP
        {
            get { return downloadFromHTTP; }
            set {
                if (value != this.downloadFromHTTP)
                {
                    this.downloadFromHTTP = value;
                    NotifyPropertyChanged("DownloadFromHTTP");
                }
            }
        }
        public bool DownloadFromFTP
        {
            get { return downloadFromFTP; }
            set
            {
                if (value != this.downloadFromFTP)
                {
                    this.downloadFromFTP = value;
                    NotifyPropertyChanged("DownloadFromFTP");
                }
            }
        }
        public bool DownloadFromUSB
        {
            get { return downloadFromUSB; }
            set
            {
                if (value != this.downloadFromUSB)
                {
                    this.downloadFromUSB = value;
                    NotifyPropertyChanged("DownloadFromUSB");
                }
            }
        }

        public string SourceServer
        {
            get { return this.sourceServer; }
            set
            {
                if (value != this.sourceServer)
                {
                    this.sourceServer = value;
                    NotifyPropertyChanged("SourceServer");
                }
            }
        }
        public string SourceFile
        {
            get { return this.sourceFile; }
            set
            {
                if (value != this.sourceFile)
                {
                    this.sourceFile = value;
                    NotifyPropertyChanged("SourceFile");
                }
            }
        }
        public bool SourceAuthenticationIsRequried
        {
            get { return sourceAuthenticationIsRequried; }
            set {
                if (value != this.sourceAuthenticationIsRequried)
                {
                    this.sourceAuthenticationIsRequried = value;
                    NotifyPropertyChanged("SourceAuthenticationIsRequried");
                }
            }
        }
        public string SourceUser
        {
            get { return sourceUser; }
            set {
                if (value != this.sourceUser)
                {
                    this.sourceUser = value;
                    NotifyPropertyChanged("SourceUser");
                }
            }
        }
        public string SourcePassword
        {
            get { return sourcePassword; }
            set {
                if (value != this.sourcePassword)
                {
                    this.sourcePassword = value;
                    NotifyPropertyChanged("SourcePassword");
                }
            }
        }
        public bool UseProxy
        {
            get { return useProxy; }
            set {
                if (value != this.useProxy)
                {
                    this.useProxy = value;
                    NotifyPropertyChanged("UseProxy");
                }
            }
        }
        public string ProxyAddress
        {
            get { return proxyAddress; }
            set {
                if (value != this.proxyAddress)
                {
                    this.proxyAddress = value;
                    NotifyPropertyChanged("ProxyAddress");
                }
                proxyAddress = value; 
            }
        }
        public bool ProxyAuthenticationIsRequried
        {
            get { return proxyAuthenticationIsRequried; }
            set {
                if (value != this.proxyAuthenticationIsRequried)
                {
                    this.proxyAuthenticationIsRequried = value;
                    NotifyPropertyChanged("ProxyAuthenticationIsRequried");
                }
            }
        }
        public string ProxyUser
        {
            get { return proxyUser; }
            set {
                if (value != this.proxyUser)
                {
                    this.proxyUser = value;
                    NotifyPropertyChanged("ProxyUser");
                }
            }
        }
        public string ProxyPassword
        {
            get { return proxyPassword; }
            set {
                if (value != this.proxyPassword)
                {
                    this.proxyPassword = value;
                    NotifyPropertyChanged("ProxyPassword");
                } 
            }
        }

        static public UpdateSettings ReadSetting() 
        {
            try
            {
                using (RegistryKey key = Registry.LocalMachine.OpenSubKey(REGISTRY_SETTINGS_KEY, true))
                {
                    XmlSerializer deserializer = new XmlSerializer(typeof(UpdateSettings));
                    using (TextReader text = new StringReader(key.GetValue("settings").ToString()))
                    {
                        return (UpdateSettings)deserializer.Deserialize(text);
                    }
                }
            }
            catch { }
            return new UpdateSettings();
        }
        public void SaveSettings()
        {
            try
            {
                using (RegistryKey key = Registry.LocalMachine.OpenSubKey(REGISTRY_SETTINGS_KEY, true))
                {
                    XmlSerializer serializer = new XmlSerializer(typeof(UpdateSettings));
                    TextWriter text = new StringWriter();
                    serializer.Serialize(text, this);
                    key.SetValue("settings", text.ToString());
                }
            }
            catch { }
        }
    }

    // Define a class to hold custom event info
    public enum Status { WrongPlatform, Progress, Finish, Error }

    public class ProgressEventArgs : EventArgs  
    {
        
        public ProgressEventArgs( Status _step, int prog, string msg )
        {
            step = _step;
            progress = prog;
            message  = msg;
        }

        private Status step;
        private int progress;
        private string message;

        public Status Step
        {
            get { return step; }
            set { step = value; }
        }

        public int Progress
        {
            get { return progress; }
            set { progress = value; }
        }

        public string Message
        {
            get { return message;  }
            set { message = value; }
        }
    }
    public abstract class DownloadFrom 
    {
        protected string CurrentPlatform = "VIA";
        protected Version CurrentVersion = new Version("0.0.0");

        protected const string REGISTRY_KEY_NAME = "Platform";
#if WindowsCE
        protected const string distFileName = "\\Жесткий диск\\nk.bin.new";
        protected const string systemFileName = "\\Жесткий диск\\nk.bin";
        protected const string backupFileName = "\\Жесткий диск\\nk.bin.org";
#else
        protected const string distFileName = "C:\\Temp\\nk.bin.new";
        protected const string systemFileName = "C:\\Temp\\nk.bin";
        protected const string backupFileName = "C:\\Temp\\nk.bin.org";

#endif
        protected UpdateSettings settings;

        public UpdateSettings Settings
        {
            get { return settings; }
            set { settings = value; }
        }

        protected Stream stream;
        //protected long fileSize;
        protected Thread thread = null;

        public event EventHandler<ProgressEventArgs> OnProgressChangedEvent;
        protected virtual void RaiseOnProgressChangedEvent(ProgressEventArgs e)
        {           
            EventHandler<ProgressEventArgs> handler = OnProgressChangedEvent;

            // Event will be null if there are no subscribers
            if (handler != null)
            {
                handler(this, e);
            }
        }

        public abstract void CheckConnection();

        public void SetupProxy()
        {
            UseProxyForm pf = new UseProxyForm();

            pf.UseProxy = this.settings.UseProxy;
            pf.ProxyAddress = this.settings.ProxyAddress;
            pf.ProxyAuthenticationIsRequried = this.settings.ProxyAuthenticationIsRequried;
            pf.ProxyUser = this.settings.ProxyUser;
            pf.ProxyPassword = this.settings.ProxyPassword;

            if (pf.ShowDialog() == DialogResult.OK)
            {
                this.settings.UseProxy = pf.UseProxy;
                this.settings.ProxyAddress = pf.ProxyAddress;
                this.settings.ProxyAuthenticationIsRequried = pf.ProxyAuthenticationIsRequried;
                this.settings.ProxyUser = pf.ProxyUser;
                this.settings.ProxyPassword = pf.ProxyPassword;
            }
        }
        public WebProxy GetProxy()
        {
            if (this.settings.UseProxy)
            {
                WebProxy webProxy = new WebProxy(); ;
                webProxy.Address = new Uri(this.settings.ProxyAddress);

                if (this.settings.ProxyAuthenticationIsRequried)
                {
                    webProxy.Credentials = new NetworkCredential(this.settings.ProxyUser, this.settings.ProxyPassword);
                }
                else
                {
                    //webProxy.UseDefaultCredentials = true;
                }
                return webProxy;
            }
            return null;
        }

        public void DoDownload()
        {
            Thread.Sleep(200);

            try
            {
                Mimoza.Common.GlobalSettings.InitLicenceFile();

                if (File.Exists(distFileName))
                {
                    File.Delete(distFileName);
                }

                if (File.Exists(backupFileName))
                {
                    File.Delete(backupFileName);
                }                

                using (MemoryStream memoryStream = new MemoryStream())
                using (FileStream   outputStream = new FileStream(distFileName, FileMode.Create))
                {
                    UpdatesFileInfo info = new UpdatesFileInfo();

                    int progress;
                    long readed = 0;
                    int readCount;
                    int bufferSize = 2048;                    
                    byte[] buffer = new byte[2048];

                    info.ReadInfo(stream);
                    if (Mimoza.Common.GlobalSettings.Licence.ModelName != info.ModelName )
                    {
                        RaiseOnProgressChangedEvent(new ProgressEventArgs(Status.WrongPlatform, 0, info.ModelName));
                        return;
                    }
                    
                    #region Read new NK.BIN from stream and write it into memory

                    RaiseOnProgressChangedEvent(new ProgressEventArgs(Status.Progress, 0, "0"));

                    readCount = stream.Read(buffer, 0, bufferSize);
                    readed = readCount;
                    while (readCount > 0)
                    {
                        memoryStream.Write(buffer, 0, readCount);
                        readCount = stream.Read(buffer, 0, bufferSize);
                        readed += readCount;
                        progress = (int)((50 * readed) / info.Size);
                        //if (progress > 100) progress = 100;

                        RaiseOnProgressChangedEvent(new ProgressEventArgs(Status.Progress, progress, ""));
                    }

                    if (readed != info.Size)
                    {
                        throw new Exception("Process aborted");
                    }

                    #endregion

                    #region Write new NK.BIN from memory to the file NK.BIN.NEW
                    memoryStream.Position = 0;
                    readCount = memoryStream.Read(buffer, 0, bufferSize);
                    readed = readCount;
                    while (readCount > 0)
                    {
                        outputStream.Write(buffer, 0, readCount);
                        readCount = memoryStream.Read(buffer, 0, bufferSize);
                        readed += readCount;
                        progress = (int)((50 * readed) / info.Size);
                        if (progress > 100) progress = 100;

                        RaiseOnProgressChangedEvent(new ProgressEventArgs(Status.Progress, 50 + progress, ""));
                    }

                    RaiseOnProgressChangedEvent(new ProgressEventArgs(Status.Progress, 100, ""));
                    outputStream.Close();
                    #endregion
                }

                #region Replace old NK.BIN with new NK.BIN
                if (File.Exists(systemFileName))
                {
                    if (File.Exists(backupFileName))
                    {
                        File.Delete(backupFileName);
                    }
                    File.Move(systemFileName, backupFileName);
                }
                File.Move(distFileName, systemFileName);
                #endregion

                RaiseOnProgressChangedEvent(new ProgressEventArgs(Status.Finish, 0, ""));
            } catch (Exception e) 
            {
                RaiseOnProgressChangedEvent(new ProgressEventArgs(Status.Error, 0, e.Message));
            }
        }

        public void CancelDownloading()
        {
            if (thread != null)
            {
                thread.Abort();
                //thread.Join();
            }
        }
        public void Download()
        {
            thread = new Thread(this.DoDownload);
            thread.Start();
        }
    }
    public class DownloadFromHTTP: DownloadFrom
    {      
        public override void CheckConnection()
        {
            // Create a new WebClient instance.
            WebRequest webRequest = WebRequest.Create(this.settings.SourceFile);
            WebProxy proxy = this.GetProxy();
            if (proxy != null)
            {
                webRequest.Proxy = proxy;
            }

            WebResponse response = webRequest.GetResponse();

            stream   = response.GetResponseStream();
        }
     }
    public class DownloadFromFTP : DownloadFrom
    {
        public override void CheckConnection()
        {
            FTPSClient client = new FTPSClient();

            if (this.settings.SourceAuthenticationIsRequried)
            {
                NetworkCredential credential = new NetworkCredential(this.settings.SourceUser, this.settings.SourcePassword);
                client.Connect(this.settings.SourceServer, 21,credential);
            }
            else {
                client.Connect(this.settings.SourceServer,21,null);
            }

            client.SetTransferMode(ETransferMode.Binary);
   
            stream = client.GetFile(this.settings.SourceFile); 
        }
    }
    public class DownloadFromUSB : DownloadFrom
    {
        public override void CheckConnection()
        {
            stream = new FileStream(settings.SourceFile, FileMode.Open, FileAccess.Read);  
        }
    }
}
